package com.macbulldev.poc.testgin.domain;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.Transient;
import javax.persistence.Version;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import net.youngdev.util.encryption.MD5Util;

import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.hibernate.Session;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.transaction.annotation.Transactional;
import org.tuckey.web.filters.urlrewrite.utils.StringUtils;

@javax.persistence.Entity
@Configurable
public class AppUser implements Authentication
{
	private static final Logger LOGGER = Logger.getLogger(AppUser.class);

    @Email(message="Invalid Email format.  Valid format: user@example.com")
    @NotEmpty(message="Email cannot be empty")
    private String email;

    @NotEmpty
    @Column(nullable=false)
    private String name;

    @NotEmpty
    @Column(nullable=false)
    @Size(min=5)
    private String password;
    
    private String confirmPassword;

    @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP)
    @org.springframework.format.annotation.DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss.SSS")
    private java.util.Date createTs;

    private Boolean isEnabled;
    private Boolean isApproved;

    private String company;
    
    private String title;

    private String addressLine1;

    private String addressLine2;

    private String postalCode;

    private String city;

    private String stateOrProvince;

    private String country;

    @NotEmpty
//	@Pattern(regexp="^\\(\\d{3}\\)[\\s]*\\d{3}[\\s]*-[\\s]*\\d{4}$", message="Phone Number must match a valid format [(999)999-9999].")
    @Column(nullable=false,length=20)
	private String phoneNumber;

	@Column(length=20,nullable=true)
    private String faxNumber;

    private String comment;

    private Set<AppRole> roles; 
    
    @Id
    @Column(name = "email")     
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

	@Override
	@Transient
	public Collection<GrantedAuthority> getAuthorities() {
		Collection<GrantedAuthority> col=new ArrayList<GrantedAuthority>();
		if (this.getRoles()!=null)
		{
			for (GrantedAuthority auth: this.getRoles())
			{
				col.add(auth);
			}
		}
			
		return col;
	}

	@Override
	@Transient
	public Object getCredentials() {
		
		return this.getPrincipal();
	}

	@Override
	@Transient
	@JsonIgnore
	public Object getDetails() {
		Object obj=null;
		try
		{
			obj=this.toJson();
		}
		catch (Exception e)
		{
			LOGGER.debug("Unable to return valid values for this.toJson()");
		}
		return obj;
	}

	@Override
	@Transient
	public Object getPrincipal() {
		
		return this.getEmail();
	}

	@Override
	@Transient
	public boolean isAuthenticated() {
		return  (this.name != null) 
		&& (this.getAuthorities()!=null ) 
		&& (this.getAuthorities().size()>0)
		&& (this.isEnabled)
		&& (this.isApproved);
	}

	@Override
	public void setAuthenticated(boolean isAuthenticated)
			throws IllegalArgumentException 
	{
		// do nothing
	}
	
	@OneToMany(cascade = CascadeType.ALL, 
			fetch = FetchType.EAGER, mappedBy = "appUser")
	public Set<AppRole> getRoles() {
		return roles;
	}

	@Transient
	public List<String> getRoleStrings()
	{
		List<String> roleStrings = new ArrayList<String>();
		if (this.roles!=null)
		{
			for (AppRole r1: this.roles)
			{
				roleStrings.add(r1.getRoleString());
			}
		}
		return roleStrings;
	}
	
	public String getName() {    
	        return this.name;        
	}    
	    
	public void setName(String name) {    
	        this.name = name;        
	}    
	
	 public String toJson() throws JsonGenerationException, JsonMappingException, IOException 
	    {
	    	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	    	
	    	ObjectMapper mapper = new ObjectMapper();
	    	mapper.configure(SerializationConfig.Feature.USE_STATIC_TYPING, true);
	    	mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
	    	mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
	    	
	    	mapper.writeValue(baos, this);
	    	return new String(baos.toByteArray());
	    }
	

	@PersistenceContext    
    transient EntityManager entityManager;


	@Version    
    @Column(name = "version")    
    private Integer version;

	public Integer getVersion() {    
        return this.version;        
    }

	public void setVersion(Integer version) {    
        this.version = version;        
    }

	@Transactional    
    public void persist() {    
    	if (!StringUtils.isBlank(this.getPassword()) 
    			&& !this.getPassword().startsWith(MD5Util.MD5_PREFIX))
    	{
    		this.setPassword(MD5Util.getMD5Base64WithLabel(this.getPassword()));
    	}
        if (this.entityManager == null) this.entityManager = entityManager();        
        this.entityManager.persist(this);        
    }
	

	@Transactional    
    public void remove() {    
        if (this.entityManager == null) this.entityManager = entityManager();        
        if (this.entityManager.contains(this)) {        
            this.entityManager.remove(this);            
        } else {        
            AppUser attached = this.entityManager.find(AppUser.class, this.email);            
            this.entityManager.remove(attached);            
        }        
    }

	@Transactional    
    public void flush() {    
        if (this.entityManager == null) this.entityManager = entityManager();        
        this.entityManager.flush();        
    }
	/** Hibernate specific.  This will not work with a different provider*/
	@Transactional    
    public void detach() { 
	 org.hibernate.Session session = (Session) entityManager().getDelegate();
	    session.evict(this);
	}
	@Transactional    
    public void merge() {   
		
		if (!StringUtils.isBlank(this.getPassword()) 
    			&& !this.getPassword().startsWith(MD5Util.MD5_PREFIX))
    	{
    		this.setPassword(MD5Util.getMD5Base64WithLabel(this.getPassword()));
    	}
        if (this.entityManager == null) this.entityManager = entityManager(); 
        
        AppUser merged = this.entityManager.merge(this);        
        this.entityManager.flush();        
      
    }

	public static final EntityManager entityManager() {    
        EntityManager em = new AppUser().entityManager;        
        if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");        
        return em;        
    }

	public static long countAppUsers() {    
        return (Long) entityManager().createQuery("select count(o) from AppUser o").getSingleResult();        
    }

	public static List<AppUser> findAllAppUsers() {    
        return entityManager().createQuery("select o from AppUser o").getResultList();        
    }

	public static AppUser findAppUser(String id) {    
        if (id == null || 0 == id.length()) throw new IllegalArgumentException("An identifier is required to retrieve an instance of AppUser");        
        return entityManager().find(AppUser.class, id);        
    }
	
	public static AppUser getByEmail(String id) {    
        if (id == null || 0 == id.length()) throw new IllegalArgumentException("An identifier is required to retrieve an instance of AppUser");        
        return entityManager().find(AppUser.class, id);        
    }

	public static List<AppUser> findAppUserEntries(int firstResult, int maxResults) {    
        return entityManager().createQuery("select o from AppUser o order by o.email asc").setFirstResult(firstResult).setMaxResults(maxResults).getResultList();        
    }
	
	public static List<AppUser> findUsersByRole(String roleString) {
		String hql ="select o from AppUser o left join fetch o.roles roles where roles.roleString=?";
	    return entityManager().createQuery(hql).setParameter(1, roleString).getResultList();        
	}
	
	public static List<AppUser> findUnapproved(int firstResult, int pageSize) {
		String hql ="select o from AppUser o left join fetch o.roles roles where o.isApproved=false order by o.createTs desc";
		Query q = entityManager().createQuery(hql);
		q.setFirstResult(firstResult);
		q.setMaxResults(pageSize);
		return q.getResultList();
	}

	public static Long countUnapproved() {
		String hql ="select count(o) from AppUser o where o.isApproved=false ";
		Query q = entityManager().createQuery(hql);

		return (Long)q.getSingleResult();
	}
	
	public static AppUser getByEmailandPassword(String email,
			String password) {
		if (!StringUtils.isBlank(password) 
    			&& !password.startsWith(MD5Util.MD5_PREFIX))
    	{
    		password=MD5Util.getMD5Base64WithLabel(password);
    	}
		AppUser user =null;

			String hql ="select o from AppUser o where lower(trim(o.email))=lower(trim(:email)) "
				+" and trim(o.password)=trim(:password) "
				+" and o.email is not null and o.password is not null";

			Query q = entityManager().createQuery(hql);
			LOGGER.debug("Query="+hql);
		
			q.setParameter("email", email);
			q.setParameter("password", password);
			List<AppUser> userList = q.getResultList();
			if (userList!=null && userList.size()>0)
			{
				LOGGER.debug("returning first user in a list of size "+userList.size());
				user= userList.get(0);
			}
			else
			{
				LOGGER.error("No valid user found.  Either userList is null ("
						+(userList==null)+") or userList has 0 entries ("
						+((userList==null)?false:(userList.size()<1))+")");
			}
		return user;
	}

	public String getPassword() {    
        return this.password;        
    }

	public void setPassword(String password) {    
        this.password = password;        
    }

	@Transient
	public String getConfirmPassword() {    
        return this.confirmPassword;        
    }

	public void setConfirmPassword(String confirmPassword) {    
        this.confirmPassword = confirmPassword;        
    }

	public Date getCreateTs() {    
        return this.createTs;        
    }

	public void setCreateTs(Date createTs) {    
        this.createTs = createTs;        
    }

	public Boolean getIsEnabled() {    
        return this.isEnabled;        
    }

	public void setIsEnabled(Boolean isEnabled) {    
        this.isEnabled = isEnabled;        
    }
	
	public Boolean getIsApproved() {
		return isApproved;
	}

	public void setIsApproved(Boolean isApproved) {
		this.isApproved = isApproved;
	}

	public String getCompany() {    
        return this.company;        
    }

	public void setCompany(String company) {    
        this.company = company;        
    }

	public String getTitle() {    
        return this.title;        
    }

	public void setTitle(String title) {    
        this.title = title;        
    }

	public String getAddressLine1() {    
        return this.addressLine1;        
    }

	public void setAddressLine1(String addressLine1) {    
        this.addressLine1 = addressLine1;        
    }

	public String getAddressLine2() {    
        return this.addressLine2;        
    }

	public void setAddressLine2(String addressLine2) {    
        this.addressLine2 = addressLine2;        
    }

	public String getPostalCode() {    
        return this.postalCode;        
    }

	public void setPostalCode(String postalCode) {    
        this.postalCode = postalCode;        
    }

	public String getCity() {    
        return this.city;        
    }

	public void setCity(String city) {    
        this.city = city;        
    }

	public String getStateOrProvince() {    
        return this.stateOrProvince;        
    }

	public void setStateOrProvince(String stateOrProvince) {    
        this.stateOrProvince = stateOrProvince;        
    }

	public String getCountry() {    
        return this.country;        
    }

	public void setCountry(String country) {    
        this.country = country;        
    }

	public String getPhoneNumber() {    
        return this.phoneNumber;        
    }

	public void setPhoneNumber(String phoneNumber) {    
        this.phoneNumber = phoneNumber;        
    }

	public String getFaxNumber() {    
        return this.faxNumber;        
    }

	public void setFaxNumber(String faxNumber) {    
        this.faxNumber = faxNumber;        
    }

	public String getComment() {    
        return this.comment;        
    }

	public void setComment(String comment) {    
        this.comment = comment;        
    }

	public void setRoles(Set<AppRole> roles) {    
        this.roles = roles;        
    }

	public String toString() {    
        StringBuilder sb = new StringBuilder();        
        sb.append("Email: ").append(getEmail()).append(", ");        
        sb.append("Name: ").append(getName()).append(", ");              
        sb.append("Version: ").append(getVersion()).append(" ");        
        return sb.toString();        
    }

	public void addRole(String role) {
		if (this.getRoles()==null)
		{
			this.setRoles(new HashSet<AppRole>());
		}
		this.getRoles().add(new AppRole(this,role));
		
	}





	
}
